package main;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Timer;
import java.util.concurrent.ArrayBlockingQueue;

public class Sync_app {
  private static final int MILLISEC_IN_SEC = 1000;
  private static final int SERVER_SOCKET_QUEUE_SIZE = 10;
  private static final String PROPERITES_FILE_NAME = "config.properties";
  public static final String END_FILE_STRING = "032732364_037085917";
  public static String neighbors[];
  public static final int HELLO_MSG_VAL = '0';
  public static final String HELLO_MSG_STR = "0";
  public static final int GET_MSG_VAL = '1';
  public static final String GET_MSG_STR = "1";
  public static String path;
  public static int myPort;
  public static double syncTime;
  public static ArrayList<Address> neighborsAddress;
  public static File myFile;
  // public static long LastModifiedLong;
  private static long realModifiedTime;
  private static long writeModifiedTime;
  // A queue to hold incoming sockets before they are handled
  private static ArrayBlockingQueue<Socket> socketsQueue;
  
  /**
   * @param args
   */
  public static void main(String[] args) {
    loadProperties();
    // Locate the file on disk
    myFile = new File(path);
    if (!myFile.exists())
      try {
        myFile.createNewFile();
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    // start a new thread that listens to sockets from clients
    socketsQueue = new ArrayBlockingQueue<Socket>(SERVER_SOCKET_QUEUE_SIZE);
    ServerListenerThread serverListener = new ServerListenerThread(myPort, socketsQueue);
    serverListener.start();
    // Schedule to run after every syncTime seconds
    Double d = syncTime * MILLISEC_IN_SEC;
    for (Address a : neighborsAddress) {
      Timer timer = new Timer();
      timer.scheduleAtFixedRate(new ClientThread(a), 0, d.intValue());
    }
  }
  
  public static byte[] readDataFile() throws IOException {
    // Using streams, read the file as bytes array into "mybytearray"
    byte[] mybytearray = new byte[(int) myFile.length()];
    FileInputStream fis;
    fis = new FileInputStream(myFile);
    BufferedInputStream bis = new BufferedInputStream(fis);
    bis.read(mybytearray, 0, mybytearray.length);
    bis.close();
    return mybytearray;
  }
  
  public static long getLastModified() {
    if (!myFile.exists())
      return 0;
    long fileModified = myFile.lastModified();
    if (fileModified <= writeModifiedTime)
      return realModifiedTime;
    return fileModified;
  }
  
  public static void setLastModifiedAfterWrite(long fileTimeStamp) {
    writeModifiedTime = myFile.lastModified();
    realModifiedTime = fileTimeStamp;
  }
  
  private static void loadProperties() {
    Properties prop = new Properties();
    try {
      // load a properties file
      prop.load(new FileInputStream(PROPERITES_FILE_NAME));
      // get the property value
      path = prop.getProperty("path");
      try {
        myPort = Integer.valueOf(prop.getProperty("myport"));
        syncTime = Double.valueOf(prop.getProperty("synctime"));
      } catch (NumberFormatException ex) {
        System.out.println("The config file contains invalid information");
        System.exit(1);
      }
      String neighbors[] = prop.getProperty("neighbors").split(";");
      neighborsAddress = new ArrayList<Address>();
      for (String s : neighbors)
        neighborsAddress.add(new Address(s));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
  
  // write the id1_id2 string to the end of data file only if the string is
  // not already there
  public static void writeEndOfFileString() {
    try {
      if (!myFile.exists())
        myFile.createNewFile();
      else {
        byte[] mybytearray = new byte[END_FILE_STRING.length()];
        FileInputStream fis;
        fis = new FileInputStream(myFile);
        BufferedInputStream bis = new BufferedInputStream(fis);
        int endLineOffset = (int) myFile.length() - END_FILE_STRING.length();
        if (endLineOffset >= 0) {
          bis.skip(endLineOffset);
          bis.read(mybytearray, 0, END_FILE_STRING.length());
          bis.close();
          String s = new String(mybytearray);
          if (s.equals(END_FILE_STRING))
            return;
        }
      }
      long timeBefore = getLastModified();
      FileWriter fw = new FileWriter(myFile.getAbsoluteFile(), true);
      BufferedWriter bw = new BufferedWriter(fw);
      bw.write("\n" + END_FILE_STRING);
      setLastModifiedAfterWrite(timeBefore);
      bw.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  
  public static class Address {
    public String ip;
    public int port;
    
    public Address(String address) {
      String[] splitAdress = address.split(":|,");
      ip = splitAdress[0];
      port = Integer.valueOf(splitAdress[1]);
    }
  }
}
